/* Ethernet Layer 2 parser */

/* Copyright (C) 2009-2011 David Zanetti
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */


/* this is mostly because .. well.. some of the packet structures are
 * the wrong way around for our architecture */

#include <stdio.h>
#include <avr/pgmspace.h>
#include "ether.h"
#include "global.h"
#include <util/delay.h>
#include "encx24j600.h"
#include "serial.h"
/* Supported layer 3 protocols */
#include "arp.h"
#include "ip.h"

typedef struct {
		uint8_t dest_mac[6];
		uint8_t src_mac[6];
		uint8_t type[2]; /* also length on strange non-IP networks */
} ether_packet_t;

uint16_t ether_type(void *pkt) {
	return (((ether_packet_t *)pkt)->type[0] << 8) |
			(((ether_packet_t *)pkt)->type[1]);
}

uint8_t *ether_payload(uint8_t *pkt) {
	return pkt+14; /* 14 byte header for ethernet */
} 

uint8_t ether_reply(uint8_t *pkt, uint8_t *newpkt) {
	uint8_t n;
	/* this just sets up the packet to be the same protocol type */
	/* copy source into destination */
	for (n = 0; n < 6; n++) {
		((ether_packet_t *)newpkt)->dest_mac[n] = ((ether_packet_t *)pkt)->src_mac[n];
	}
	for (n = 0; n < 6; n++) {
		((ether_packet_t *)newpkt)->src_mac[n] = eth_mac[n];
	}
	((ether_packet_t *)newpkt)->type[0] = ((ether_packet_t *)pkt)->type[0];
	((ether_packet_t *)newpkt)->type[1] = ((ether_packet_t *)pkt)->type[1];

	return 14; /* number of bytes written to packet */
}

uint8_t ether_isbroadcast(uint8_t *pkt) {
	if (((ether_packet_t *)pkt)->dest_mac[0] & 0x1) {
		/* lead bit is 1 on the wire == broadcast */
		return 1;
	}
	return 0;
}

/* function to read and parse the begining of an Ethernet packet, allocate
   the correct parser to deal with it */
/* Since we always start with the begining of the packet, we don't need
   to have any args */
void ether_process(void) {
	ether_packet_t pkt, tx;
	uint8_t status, n;
	uint8_t slot, result = E_NOTHING;

	/* retrieve status of this packet */
	status = encx24j600_rx_good();

	/* discard bad packets */
	if (! status & E_RX_GOOD) {
#ifdef DEBUG_ETHER
		serial0_tx_PGM(PSTR("ether: discarding bad packet\r\n"));
#endif
		encx24j600_rx_complete();
		return;
	}

	/* discard packets with length instead of type. IP Suite always uses
    type not length */
	if (! status & E_RX_TYPE) {
#ifdef DEBUG_ETHER
		serial0_tx_PGM(PSTR("ether: discarding packet using length not type\r\n"));
#endif
		encx24j600_rx_complete();
		return;
	}

	/* we have a good packet to work with, now populate our packet struct */
	if (encx24j600_rx(&pkt,sizeof(ether_packet_t)) != sizeof(ether_packet_t)) {
		/* short frame or something, discard */
		serial0_tx_PGM(PSTR("ether: short frame, discarding\r\n"));
		encx24j600_rx_complete();
		return;
	}

#ifdef DEBUG_ETHER 
	serial0_tx_PGM(PSTR("ether: "));
	for (n = 0; n < 6; n++) {
		serial0_tx_hex(pkt.src_mac[n]);
	}
	serial0_tx_PGM(PSTR("->"));
	for (n = 0; n < 6; n++) {
		serial0_tx_hex(pkt.dest_mac[n]);
	}
	serial0_tx_PGM(PSTR(" ["));
	serial0_tx_hex(pkt.type[0]);
	serial0_tx_hex(pkt.type[1]);
	serial0_tx_cout(']');
	if (status & E_RX_BROADCAST) {
		serial0_tx_PGM(PSTR(" (broadcast)"));
	} else if (status & E_RX_MULTICAST) {
		serial0_tx_PGM(PSTR(" (multicast)"));
	}
	serial0_tx_cr();
#endif

	/* pkt now contains the ethernet header */

	/* obtain a write slot, so lower layers have somewhere to write to */
	slot = encx24j600_tx_freeslot();
	if (slot == 0xff) {
		serial0_tx_PGM(PSTR("ether: no free TX slots, discarding RX packet\r\n"));
		encx24j600_rx_complete(); /* oh well! */
		return;
	}

	switch ((pkt.type[0] << 8 | pkt.type[1])) {
		case 0x0806:
			/* Address Resolution Protocol */
			result = arp_process(slot);
			break;
		case 0x0800:
			/* Internet Protocol */
			result = ip_process(slot);
			break;
		default:
			/* some unknown format */
			break;
	}

	if (result) {
		/* generate reply header */
		tx.type[0] = pkt.type[0];
		tx.type[1] = pkt.type[1];
		/* if this is a broadcast reply, populate the broadcast MAC address */
		if (result == E_REPLY_BROAD) {
			for (n = 0; n < 6; n++) {
				tx.src_mac[n] = eth_mac[n];
				tx.dest_mac[n] = 0xff;
			}
		} else {
			/* normal case, use source MAC to send back to */
			for (n = 0; n < 6; n++) {
				tx.src_mac[n] = eth_mac[n];
				tx.dest_mac[n] = pkt.src_mac[n];
			}
		}
		/* prepend and transmit */
		if (encx24j600_tx_prepend(slot,(uint8_t *)&tx,sizeof(ether_packet_t))) {
			/* transmit */
			encx24j600_tx_slot(slot);
		}
	} else {
		encx24j600_slot_done(slot);
	}

	/* we have completed processing the packet, finish up */
	encx24j600_rx_complete();

	return;
}

/* Send the given slot to the MAC address given, with ethertype type. This assumes someone else knows why we're sending to this specific MAC */
uint8_t ether_send(uint8_t *dest_mac, uint16_t type, uint8_t slot) {
	ether_packet_t pkt;
	uint8_t n;

#ifdef DEBUG_ETHER
	serial0_tx_PGM(PSTR("es "));
	serial0_tx_hex(type >> 8);
	serial0_tx_hex(type & 0xff);
	serial0_tx_cout('-');
	for (n = 0; n < 6; n++) {
		serial0_tx_hex(dest_mac[n]);
	}
	serial0_tx_cout('-');
	serial0_tx_dec(encx24j600_slot_len(slot));
	serial0_tx_cr();
#endif

	/* populate our header */
	pkt.type[0] = (type >> 8);
	pkt.type[1] = (type & 0xff);
	for (n = 0; n < 6; n++) {
		pkt.src_mac[n] = eth_mac[n];
		pkt.dest_mac[n] = dest_mac[n];
	}
	/* prepend and transmit */
	if (encx24j600_tx_prepend(slot,(uint8_t *)&pkt,sizeof(ether_packet_t))) {
		/* do transmit */
		encx24j600_tx_slot(slot);
		return 1;
	} else {
		serial0_tx_PGM(PSTR("ether: failed to send packet\r\n"));
		encx24j600_slot_done(slot);
	}
	return 0;
}
